package com.example.day4.analysis;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.TreeMap;
import java.util.stream.Stream;

import static java.util.stream.Collectors.counting;
import static java.util.stream.Collectors.groupingBy;

public class AnalysisTest {
    /*
        数据格式
        0       1       2       3       4       5       6     7      8        9     10    11
        序号    下单时间  订单编号  商品编号 类别编号  类别码   品牌   价格   用户编号   年龄   性别   地区
     */
    static final int INDEX = 0;
    static final int TIME = 1;
    static final int ORDER_ID = 2;
    static final int PRODUCT_ID = 3;
    static final int CATEGORY_ID = 4;
    static final int CATEGORY_CODE = 5;
    static final int BRAND = 6;
    static final int PRICE = 7;
    static final int USER_ID = 8;
    static final int USER_AGE = 9;
    static final int USER_SEX = 10;
    static final int USER_REGION = 11;
    static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");

    public static void main(String[] args) {
        case8();
    }

    /**
     * 1.统计每月销售量
     */
    private static void case1() {
        // 思路:
        // 首先跳过流中的第一条,在把流中的数据根据逗号分隔,得到一个数组,在根据时间进行分组
        try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
            TreeMap<YearMonth, Long> collect = lines.skip(1)
                    .map(line -> line.split(","))
                    // 由于默认的groupingBy是HashMap,收集到流中的数据是无序的,所以需要在第二个参数指定收集到哪一个集合中
                    // YearMonth.from()该方法可以得到一个完整日期对象的年月部分
                    // counting()是下游收集器,该方法可以统计流中的元素个数
                    .collect(groupingBy(array -> YearMonth.from(formatter.parse(array[TIME])), TreeMap::new, counting()));
            collect.forEach((k, v) -> System.out.println(k + "订单数: " + v));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 2.统计销售量最高的月份
     */
    private static void case2() {
        // 思路:
        // 在第一天的思路中,我们已经根据时间进行分组,现在只需要对分组后的数据进行处理,找到最大值即可
        try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
            lines.skip(1)
                    .map(line -> line.split(","))
                    .collect(groupingBy(array -> YearMonth.from(formatter.parse(array[TIME])), counting()))
                    .entrySet().stream()
                    // .max(Comparator.comparingLong(e -> e.getValue()))
                    // 使用方法引用简化
                    // .max(Comparator.comparingLong(Map.Entry::getValue))
                    // 在Map.Entry中就提供了两个方法comparingByValue 和comparingByKey 根据值或键进行比较
                    .max(Map.Entry.comparingByValue())
                    .ifPresent(entry -> System.out.println(entry.getKey() + "订单数: " + entry.getValue()));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 3.统计销量最高的商品
     */
    private static void case3() {
        // 思路:
        // 跳过流中的第一条,在把流中的数据根据逗号分隔,得到一个数组,根据商品编号进行分组,并统计分组后的元素个数
        // 分组后得到的是一个Map集合,在Map集合中找到最大值即可
        try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
            lines.skip(1)
                    .map(line -> line.split(","))
                    .collect(groupingBy(array -> array[PRODUCT_ID], counting()))
                    .entrySet().stream()
                    .max(Map.Entry.comparingByValue())
                    .ifPresent(entry -> System.out.println(entry.getKey() + " 订单数: " + entry.getValue()));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 4.统计下单最多的前10名用户 - 方式一
     */
    private static void case4_1() {
        // 思路:
        // 跳过流中的第一条,在把流中的数据根据逗号分隔,得到一个数组,根据用户编号进行分组,并统计分组后的元素个数
        // 分组后得到一个Map集合,对Map集合进行排序,并且逆序排序,并获取前10个即可
        try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
            lines.skip(1)
                    .map(line -> line.split(","))
                    .collect(groupingBy(array -> array[USER_ID], counting()))
                    .entrySet().stream()
                    // 这里直接加reversed()方法会报错,是因为泛型推断的问题,我们只需要手动的指定下泛型就不会出错
                    .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
                    .limit(10)
                    .forEach(entry -> System.out.println(entry.getKey() + " 订单数: " + entry.getValue()));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 4.统计下单最多的前10名用户 - 方式二使用小顶堆
     */
    private static void case4_2() {
        // 思路:
        // 不和第一种思路一样,不对流中的数据进行排序,而是使用小顶堆可以提升性能
        try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
            MyQueue<Map.Entry<String, Long>> queue = lines.skip(1)
                    .map(line -> line.split(","))
                    .collect(groupingBy(array -> array[USER_ID], counting()))
                    .entrySet().stream()
                    .collect(
                            () -> new MyQueue<Map.Entry<String, Long>>(Map.Entry.comparingByValue(), 10),
                            (q, x) -> q.offer(x),
                            (q1, q2) -> q1.addAll(q2));

            // 遍历小顶堆不能直接遍历,需要先判断堆是否为空然后取出堆顶中的元素
            while (!queue.isEmpty()) {
                System.out.println(queue.poll());
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    static class MyQueue<E> extends PriorityQueue<E> {
        private int max;

        public MyQueue(Comparator<? super E> comparator, int max) {
            super(comparator);
            this.max = max;
        }

        @Override
        public boolean offer(E e) {
            boolean r = super.offer(e);
            if (this.size() > max) {
                this.remove();
            }
            return r;
        }
    }

    /**
     * 5.统计每个地区下单最多的用户
     */
    private static void case5_1() {
        try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
            Map<String, Map<String, Long>> collect = lines.skip(1)
                    .map(line -> line.split(","))
                    // 分两次组 第一次分组根据地区分组,第二次根据用户编号进行分组
                    .collect(groupingBy(array -> array[USER_REGION], groupingBy(array -> array[USER_ID], counting())));

            // 拿到分组后的map后,我们需要得到地区也就是键保持不变 值又是一个Map但是只需要里面最大的一条记录即可
            collect.entrySet().stream().map(e -> Map.entry(
                    e.getKey(),
                    // 因为值是一个Map,所以再次转换成流获取里面的最大值
                    e.getValue().entrySet().stream().max(Map.Entry.comparingByValue())
            )).forEach(System.out::println);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 5.统计每个地区下单最多的前3名用户
     */
    private static void case5_2() {
        try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
            lines.skip(1)
                    .map(line -> line.split(","))
                    // 分两次组 第一次分组根据地区分组,第二次根据用户编号进行分组
                    .collect(groupingBy(array -> array[USER_REGION], groupingBy(array -> array[USER_ID], counting())))
                    .entrySet().stream().map(
                            e -> Map.entry(
                                    e.getKey(),
                                    // 拿到分组后的map后,我们需要得到地区也就是键保持不变 值又是一个Map但是只需要里面最大三条记录即可
                                    // 方法就是对小Map进行逆序排序,然后取前3条
                                    // e.getValue().entrySet().stream().sorted(Map.Entry.<String, Long>comparingByValue().reversed()).limit(3).collect(toList())
                                    // 使用小顶堆实现
                                    e.getValue().entrySet().stream().collect(
                                            () -> new MyQueue<Map.Entry<String, Long>>(Map.Entry.comparingByValue(), 3),
                                            MyQueue::offer,
                                            MyQueue::addAll)
                            )).forEach(System.out::println);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 6.按类别统计销量
     */
    private static void case6_1() {
        try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
            lines.skip(1)
                    .map(line -> line.split(","))
                    // 过滤掉没有类别编号的
                    .filter(array -> !array[CATEGORY_CODE].isEmpty())
                    .collect(groupingBy(array -> array[CATEGORY_CODE], TreeMap::new, counting()))
                    .forEach((k, v) -> System.out.println(k + " 销量: " + v));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 6.按一级类别统计销量
     */
    private static void case6_2() {
        try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
            lines.skip(1)
                    .map(line -> line.split(","))
                    // 过滤掉没有类别编号的
                    .filter(array -> !array[CATEGORY_CODE].isEmpty())
                    // 其余的代码都是一样,就是修改下分组是第一个参数的逻辑即可,或者一级类别的字符串即可
                    // 这边可以定义一个静态方法在lambda表达式中使用方法引用简化代码有更好的可读性
                    .collect(groupingBy(AnalysisTest::firstCategory, TreeMap::new, counting()))
                    .forEach((k, v) -> System.out.println(k + " 销量: " + v));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    static String firstCategory(String[] array) {
        String str = array[CATEGORY_CODE];
        return str.substring(0, str.indexOf("."));
    }

    /**
     * 7.按价格区间统计销量
     */
    private static void case7() {
        try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
            lines.skip(1)
                    .map(line -> line.split(","))
                    // 第二次转换为Double
                    .map(array -> Double.valueOf(array[PRICE]))
                    .collect(groupingBy(AnalysisTest::priceRange, counting()))
                    .forEach((k, v) -> System.out.println(k + " 销量: " + v));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    static String priceRange(Double price) {
        if (price < 100) {
            return "[0,100)";
        } else if (price >= 100 && price < 500) {
            return "[100,500)";
        } else if (price >= 500 && price < 1000) {
            return "[500,1000)";
        } else {
            return "[1000,∞)";
        }
    }

    /**
     * 8.统计不同年龄段女性所下不同类别订单
     */
    private static void case8() {
        try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
            lines.skip(1)
                    .map(line -> line.split(","))
                    .filter(array -> array[USER_SEX].equals("女"))
                    .filter(array -> !array[CATEGORY_CODE].isEmpty())
                    // 两次收集,第一次根据年龄段分组,第二次根据一级类目分组
                    .collect(groupingBy(AnalysisTest::ageRange, groupingBy(AnalysisTest::firstCategory, TreeMap::new, counting())))
                    .forEach((k, v) -> {
                        System.out.print(k + ":");
                        v.forEach((k1, v1) -> System.out.println("\t" + k1 + ": " + v1));
                    });
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    static String ageRange(String[] array) {
        int age = Double.valueOf(array[USER_AGE]).intValue();
        if (age < 18) {
            return "[0,18)";
        } else if (age < 30) {
            return "[18,30)";
        } else if (age < 50) {
            return "[30,50)";
        } else {
            return "[50,∞)";
        }
    }
}
